package com.sailing.xjpb.common;

import com.google.common.collect.Lists;
import com.sailing.xjpb.common.orika.*;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.Converter;
import ma.glasnost.orika.CustomConverter;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.converter.ConverterFactory;
import ma.glasnost.orika.impl.DefaultCodeGenerationStrategy;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.impl.generator.CodeGenerationStrategy;
import ma.glasnost.orika.impl.generator.specification.UnmappableEnum;
import ma.glasnost.orika.metadata.ClassMapBuilder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * bean转换工具/属性拷贝工具
 *
 * @author yaowei
 */
@Slf4j
public class EJBGenerator {

    public static EJBGenerator EJB_GENERATOR = new EJBGenerator();
    /**
     * bean转换工厂
     */
    private MapperFactory mapperFactory;
    /**
     * bean转换工厂-非空字段不转
     */
    private MapperFactory falseMapNullsMapperFactory;

    private EJBGenerator() {
        register(null);
    }

    /**
     * 将当前分页结果转换为目标分页结果
     *
     * @param <T>      转换结果数据类型
     * @param <S>      转换源数据类型
     * @param page     分页结果
     * @param pageable 分页请求对象
     * @param clz      转换结果类型
     */
    public static <T, S> Page<T> convert(Page<S> page, Pageable pageable, Class<T> clz) {
        if (page == null) {
            return new PageImpl<>(Collections.emptyList(), pageable, 0);
        }
        List<S> content = page.getContent();
        long total = page.getTotalElements();
        List<T> rsList = EJBGenerator.convert(content, clz);
        return new PageImpl<>(rsList, pageable, total);
    }

    /**
     * 将当前分页结果转换为目标分页结果
     *
     * @param <T>  转换结果数据类型
     * @param <S>  转换源数据类型
     * @param page 分页结果
     * @param clz  转换结果类型
     */
    public static <T, S> Page<T> convert(Page<S> page, Class<T> clz) {
        return page.map(e -> EJBGenerator.convert(e, clz));
    }

    /**
     * 对象转换
     *
     * @param s   源对象
     * @param clz 目标对象类型
     * @param <T> 目标对象类型
     * @param <S> 源对象类型
     * @return 转换后的对象
     */
    public static <T, S> T convert(S s, Class<T> clz) {
        return getMapperFacade().map(s, clz);
    }

    /**
     * 对象转换
     *
     * @param s            源对象
     * @param clz          目标对象类型
     * @param afterConvert 复制后用于设置其他字段
     * @param <T>          目标对象类型
     * @param <S>          源对象类型
     * @return 转换后的对象
     */
    public static <T, S> T convert(S s, Class<T> clz, BiConsumer<S, T> afterConvert) {
        T t = convert(s, clz);
        if (t != null && afterConvert != null) {
            afterConvert.accept(s, t);
        }
        return t;
    }

    /**
     * 对象转换
     *
     * @param s            源对象
     * @param clz          目标对象类型
     * @param afterConvert 复制后用于设置其他字段
     * @param <T>          目标对象类型
     * @param <S>          源对象类型
     * @return 转换后的对象
     */
    public static <T, S> T convert(S s, Class<T> clz, Consumer<T> afterConvert) {
        T t = convert(s, clz);
        if (t != null && afterConvert != null) {
            afterConvert.accept(t);
        }
        return t;
    }

    /**
     * @param s            数据对象
     * @param clz          复制目标类型
     * @param afterConvert 复制后用于设置其他字段
     * @param <T>          s对象类型
     * @param <S>          目标对象类型
     * @return 转换后的对象
     */
    public static <T, S> List<T> wrap(List<S> s, Class<T> clz, BiConsumer<S, T> afterConvert) {
        if (s == null) {
            return Collections.emptyList();
        }

        if (afterConvert == null) {
            return convert(s, clz);
        } else {
            return s.stream().map(i -> {
                T t = EJBGenerator.convert(i, clz);
                afterConvert.accept(i, t);
                return t;
            }).collect(Collectors.toList());
        }
    }

    /**
     * @param s            数据对象
     * @param clz          复制目标类型
     * @param afterConvert 复制后用于设置其他字段
     * @param <T>          s对象类型
     * @param <S>          目标对象类型
     * @return 转换后的对象
     */
    public static <T, S> List<T> wrap(List<S> s, Class<T> clz, Consumer<T> afterConvert) {
        if (s == null) {
            return Collections.emptyList();
        }

        if (afterConvert == null) {
            return convert(s, clz);
        } else {
            return s.stream().map(i -> {
                T t = EJBGenerator.convert(i, clz);
                afterConvert.accept(t);
                return t;
            }).collect(Collectors.toList());
        }
    }

    /**
     * @param s   数据对象
     * @param clz 复制目标类型
     * @param <T> s对象类型
     * @param <S> 目标对象类型
     * @return 转换后的对象
     */
    public static <T, S> List<T> convert(List<S> s, Class<T> clz) {
        if (s == null) {
            return Collections.emptyList();
        }
        return getMapperFacade().mapAsList(s, clz);
    }

    /**
     * 对象属性配置
     *
     * @param sources     源对象
     * @param destination 目标对象
     */
    public static void copyProperties(final Object sources, final Object destination) {
        if (Objects.isNull(sources) || Objects.isNull(destination)) {
            return;
        }
        getMapperFacade().map(sources, destination);
    }

    /**
     * 对象属性配置
     *
     * @param sources     源对象
     * @param destination 目标对象
     */
    public static void copyNotNullProperties(final Object sources, final Object destination) {
        if (Objects.isNull(sources) || Objects.isNull(destination)) {
            return;
        }
        EJB_GENERATOR.falseMapNullsMapperFactory.getMapperFacade().map(sources, destination);
    }

    /**
     * 注册类型转换器，如果配置了converter将优先于DefaultCodeGenerationStrategy里面配置的规则
     *
     * @param converter 转换器
     * @param <S>       源字段类型
     * @param <D>       目标字段类型
     */
    public static <S, D> void registerConverter(Converter<S, D> converter) {
        ConverterFactory converterFactory = getMapperFactory().getConverterFactory();
        converterFactory.registerConverter(converter);
    }

    /**
     * 注册对象转换配置
     *
     * @param s         源数据类型
     * @param d         目标数据类型
     * @param configMap 字段映射配置
     * @param <S>       源数据类型
     * @param <D>       目标数据类型
     */
    public static <S, D> void register(Class<S> s, Class<D> d, Map<String, String> configMap) {
        ClassMapBuilder<S, D> classMapBuilder = getMapperFactory().classMap(s, d);
        configMap.forEach(classMapBuilder::field);
        classMapBuilder.byDefault().register();
    }

    /**
     * 得到bean拷贝工厂
     *
     * @return bean拷贝工厂
     */
    public static MapperFactory getMapperFactory() {
        return EJB_GENERATOR.mapperFactory;
    }

    /**
     * 注册bean转换策略
     *
     * @param userStrategy 转换策略
     */
    public void register(CodeGenerationStrategy userStrategy) {
        CodeGenerationStrategy strategy = userStrategy;
        if (strategy == null) {
            strategy = getCodeGenerationStrategy();
        }
        DefaultMapperFactory.Builder mapperBuilder = new DefaultMapperFactory.Builder();
        mapperBuilder.codeGenerationStrategy(strategy);
        this.mapperFactory = mapperBuilder.build();

        DefaultMapperFactory.Builder falseMapNullsBuilder = new DefaultMapperFactory.Builder();
        falseMapNullsBuilder.codeGenerationStrategy(strategy);
        this.falseMapNullsMapperFactory = falseMapNullsBuilder.mapNulls(false).build();
        // 初始化内置转换器
        this.getDefaultConverters().forEach(c -> {
            this.mapperFactory.getConverterFactory().registerConverter(c);
            this.falseMapNullsMapperFactory.getConverterFactory().registerConverter(c);
        });
    }

    /**
     * 得到bean拷贝工厂
     *
     * @return bean拷贝工厂
     */
    public DefaultCodeGenerationStrategy getCodeGenerationStrategy() {
        DefaultCodeGenerationStrategy strategy = new DefaultCodeGenerationStrategy();
        strategy.addSpecification(new ObjectToEnum(), CodeGenerationStrategy.Position.BEFORE, UnmappableEnum.class);
        strategy.addSpecification(new ObjectToDate(), CodeGenerationStrategy.Position.FIRST, UnmappableEnum.class);
        strategy.addSpecification(new DateToString(), CodeGenerationStrategy.Position.FIRST, UnmappableEnum.class);
        return strategy;
    }

    public static List<CustomConverter> getDefaultConverters() {
        return Lists.newArrayList(new MapToMap(),
                new ObjectToInteger(), new StringToInteger(),
                new ObjectToLong(), new StringToLong(),
                new ObjectToFloat(), new StringToFloat(),
                new ObjectToDouble(), new StringToDouble(),
                new ObjectToShort(), new StringToShort());
    }

    private static MapperFacade getMapperFacade() {
        return getMapperFactory().getMapperFacade();
    }

}
